package hyh.com.videodemo;

import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Environment;
import android.os.SystemClock;
import android.util.Log;
import android.view.SurfaceHolder;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.concurrent.LinkedBlockingQueue;

public class VideoRecorder {

    private Camera camera;
    private int cameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;
    private Camera.Parameters parameters;
    private MediaCallback cameraCallback;
    private AudioRecord audioRecord;
    private int minBufferSize;
    private MediaCallback audioCallback;
    private Object audioObj = new Object();
    private Object cameraObj = new Object();
    private boolean startFlag = false;
    private LinkedBlockingQueue<MediaData> cameraQueue = new LinkedBlockingQueue<>(100);
    private LinkedBlockingQueue <MediaData> audioQueue = new LinkedBlockingQueue<>(500);
    FileOutputStream fileOutputStream;
    private MediaCallback encodeCallback;
    private int width = 640;
    private int height = 480;

    private volatile static VideoRecorder mInstance;

    public interface MediaCallback{
        void onCallBack(byte[] data,int len);
    }

    static{
        System.loadLibrary("native-lib");
    }

    private VideoRecorder(){


        initCamera();
        initAudio();
//        initVideo(Environment.getExternalStorageDirectory()+ File.separator+"1.h264",640,480);
//        initAudio(Environment.getExternalStorageDirectory()+ File.separator+"1.aac");
        open(Environment.getExternalStorageDirectory()+ File.separator+"2.MPEG",height,width);
        try {
            fileOutputStream = new FileOutputStream(Environment.getExternalStorageDirectory()+File.separator+"a.mpeg");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
//        init(Environment.getExternalStorageDirectory()+ File.separator+"a.aac",640,480);
    }

    private void initCamera(){
        camera = Camera.open(cameraId);
        camera.setDisplayOrientation(90);
        camera.setPreviewCallback(previewCallback);
        parameters = camera.getParameters();
        parameters.setRotation(90);
        parameters.setPictureSize(width,height);
        parameters.setPreviewSize(width,height);
//        parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
        camera.setParameters(parameters);
        Log.e("androidLog","frameRate="+parameters.getPreviewFrameRate());
    }


    private void initAudio(){
        minBufferSize = AudioRecord.getMinBufferSize(44100,AudioFormat.CHANNEL_IN_STEREO,AudioFormat.ENCODING_PCM_16BIT);
        audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,44100,AudioFormat.CHANNEL_IN_STEREO,AudioFormat.ENCODING_PCM_16BIT,minBufferSize);
        Log.e("androidLog","sampleRate="+audioRecord.getSampleRate());
        Log.e("androidLog","minBufferSize="+minBufferSize);
    }

    private Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() {
        @Override
        public void onPreviewFrame(byte[] data, Camera camera) {
            if(startFlag){
                try {
//                    data = NV21ToNV12(data,width,height);
                    data = NV21_rotate_to_270(data,width,height);
                    cameraQueue.add(new MediaData(data,data.length));
                    synchronized (cameraObj){
                        cameraObj.notify();
                    }
                }catch (Exception e){

                }
            }
        }
    };


    private byte[] NV21ToNV12(byte[] nv21, int width, int height) {
        byte[] nv12 = new byte[width * height * 3 / 2];
        int frameSize = width * height;
        int i, j;
        System.arraycopy(nv21, 0, nv12, 0, frameSize);
        for (i = 0; i < frameSize; i++) { nv12[i] = nv21[i]; }
        for (j = 0; j < frameSize / 2; j += 2) {
            nv12[frameSize + j - 1] = nv21[j + frameSize];
        }
        for (j = 0; j < frameSize / 2; j += 2) {
            nv12[frameSize + j] = nv21[j + frameSize - 1];
        }
        return nv12;
    }


    private byte[] NV21_rotate_to_270(byte[] nv21_data, int width, int height)
    {
        int y_size = width * height;
        int buffser_size = y_size * 3 / 2;
        byte[] nv21_rotated = new byte[buffser_size];
        int i = 0;

        // Rotate the Y luma
        for (int x = width - 1; x >= 0; x--)
        {
            int offset = 0;
            for (int y = 0; y < height; y++)
            {
                nv21_rotated[i] = nv21_data[offset + x];
                i++;
                offset += width;
            }
        }

        // Rotate the U and V color components
        i = y_size;
        for (int x = width - 1; x > 0; x = x - 2)
        {
            int offset = y_size;
            for (int y = 0; y < height / 2; y++)
            {
                nv21_rotated[i] = nv21_data[offset + (x - 1)];
                i++;
                nv21_rotated[i] = nv21_data[offset + x];
                i++;
                offset += width;
            }
        }
        return nv21_rotated;
    }


    private byte[] rotateYUV420Degree90(byte[] data, int imageWidth, int imageHeight)
    {

        byte [] yuv = new byte[imageWidth*imageHeight*3/2];
        // Rotate the Y luma
        int count = 0;
        for(int i = 0;i < imageWidth;i++)
        {
            for(int j = imageHeight-1;j >= 0;j--)
            {
                yuv[count++] = data[j*imageWidth+i];
            }
        }

        // Rotate the U and V color components
        for(int i=0;i<imageWidth/2;i++){
            for (int j=imageHeight*3/2-1;j>=imageHeight;j--){
                yuv[count++] = data[j*imageWidth+2*i];
                yuv[count++] = data[j*imageWidth+2*i+1];
            }
        }

        return yuv;
    }

    private byte[] NV21_rotate_to_90(byte[] nv21_data, int width, int height)
    {
        int y_size = width * height;
        int buffser_size = y_size * 3 / 2;
        byte[] nv21_rotated = new byte[buffser_size];
        // Rotate the Y luma

        int i = 0;
        int startPos = (height - 1)*width;
        for (int x = 0; x < width; x++)
        {
            int offset = startPos;
            for (int y = height - 1; y >= 0; y--)
            {
                nv21_rotated[i] = nv21_data[offset + x];
                i++;
                offset -= width;
            }
        }

        // Rotate the U and V color components
        i = buffser_size - 1;
        for (int x = width - 1; x > 0; x = x - 2)
        {
            int offset = y_size;
            for (int y = 0; y < height / 2; y++)
            {
                nv21_rotated[i] = nv21_data[offset + x];
                i--;
                nv21_rotated[i] = nv21_data[offset + (x - 1)];
                i--;
                offset += width;
            }
        }
        return nv21_rotated;
    }

    public VideoRecorder setSurfaceHolder(SurfaceHolder surfaceHolder){
        try {
            camera.setPreviewDisplay(surfaceHolder);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return this;
    }

    public VideoRecorder setCameraCallback(MediaCallback cameraCallback){
        this.cameraCallback = cameraCallback;
        return this;
    }

    public VideoRecorder setAudioCallback(MediaCallback audioCallback){
        this.audioCallback = audioCallback;
        return this;
    }

    public VideoRecorder setEncodeCallback(MediaCallback encodeCallback){
        this.encodeCallback = encodeCallback;
        return this;
    }

    public void startRecord(){
        if(!startFlag){
            startFlag = true;
            camera.startPreview();
            audioRecord.startRecording();

            ThreadPoolManager.getInstance().executor(readAudioRunnable);
            ThreadPoolManager.getInstance().executor(audioRunnable);
            ThreadPoolManager.getInstance().executor(cameraRunnable);
        }
    }

    public void stopRecord(){
        if(startFlag){
            startFlag = false;
            camera.setPreviewCallback(null);
            camera.stopPreview();
            stop();
//            stopAudio();
//            stopVideo();
            try {
                fileOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private Runnable readAudioRunnable = new Runnable() {
        @Override
        public void run() {
            byte b[] = new byte[minBufferSize];
            while (startFlag){
                if(audioRecord.getRecordingState()!=AudioRecord.STATE_UNINITIALIZED){
                    int n = audioRecord.read(b,0,4096);
                    MediaData mediaData = new MediaData(b,n);
                    try{
                        audioQueue.add(mediaData);
                        synchronized (audioObj){
                            audioObj.notify();
                        }
                    }catch (Exception e){
                    }
                }
            }
        }
    };

    private Runnable audioRunnable = new Runnable() {
        @Override
        public void run() {
            while (startFlag){
                if(audioQueue.isEmpty()){
                    synchronized (audioObj){
                        try {
                            audioObj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }else{
                    MediaData mediaData = audioQueue.remove();
                    encodeAudio(mediaData.getData(),mediaData.getLen());
                }
            }
        }
    };

    private Runnable cameraRunnable = new Runnable() {
        @Override
        public void run() {
            while (startFlag){
                if(cameraQueue.isEmpty()){
                    synchronized (cameraObj){
                        try {
                            cameraObj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }else{
                    MediaData mediaData = cameraQueue.remove();
//                    try {
//                        fileOutputStream.write(mediaData.getData(),0,mediaData.getLen());
//                    } catch (IOException e) {
//                        e.printStackTrace();
//                    }
                    encodeVideo(mediaData.getData(),mediaData.getLen());
                }
            }
        }
    };

    public void onCallback(byte data[],int len){

        encodeCallback.onCallBack(data,len);
//        Log.e("androidLog","我被调用了len="+len);
    }

    public static VideoRecorder getInstance(){
        if(mInstance==null){
            synchronized (VideoRecorder.class){
                if(mInstance==null){
                    mInstance = new VideoRecorder();
                }
            }
        }
        return mInstance;
    }

    public native void initVideo(String destUrl,int width,int height);

    public native void initAudio(String destUrl);

    public native void open(String destUrl,int width,int height);

    public native void encodeVideo(byte data[],int len);

    public native void encodeAudio(byte data[],int len);

    public native void stopVideo();

    public native void stopAudio();

    public native void stop();

}
